use crate::util::{base58decode, base58encode, clear_key_raw_str, hash_pub_key, push_key_label};
use p256::ecdsa::signature::{Signer, Verifier};
use p256::ecdsa::{Signature, SigningKey, VerifyingKey};
use p256::elliptic_curve::rand_core::OsRng;
use p256::pkcs8::der::Encode;
use p256::pkcs8::{EncodePrivateKey, PrivateKeyInfo};
use p256::{NistP256, PublicKey, SecretKey};
use sha2::{Digest, Sha256};
use std::collections::HashMap;
use std::fs::OpenOptions;
use std::io::{BufRead, BufReader, Read, Write};

pub const ADDRESS_CHECKSUM_LEN: usize = 4;

pub struct Wallet {
    pub private_key: SecretKey,
    pub public_key: Vec<u8>,
}

pub struct Wallets {
    pub wallets: HashMap<String, Wallet>,
}

impl Wallet {
    pub fn new_wallet() -> Wallet {
        let (private_key, public_key) = Wallet::new_key_pair();
        Wallet {
            private_key,
            public_key,
        }
    }

    fn new_key_pair() -> (p256::elliptic_curve::SecretKey<NistP256>, Vec<u8>) {
        // Generate secret key
        let secret_key = SecretKey::random(&mut OsRng);
        // Derive public key
        let public_key = secret_key.public_key();
        (secret_key, clear_key_raw_str(&public_key.to_string()).as_bytes().to_vec())
    }

    pub fn get_address(&self) -> String {
        let pub_key_hash = hash_pub_key(&self.public_key);
        assert_eq!(pub_key_hash.len(), 20);
        let versioned_payload = [&[0], &pub_key_hash[..]].concat();
        let checksum = Wallet::checksum(&versioned_payload);
        let full_payload = [versioned_payload, checksum].concat();
        let address = base58encode(&full_payload);
        address
    }

    fn checksum(payload: &Vec<u8>) -> Vec<u8> {
        let mut hasher = Sha256::new();
        hasher.update(payload);
        let first_sha = hasher.finalize().to_vec();

        let mut hasher = Sha256::new();
        hasher.update(first_sha);
        let second_sha = hasher.finalize().to_vec();

        second_sha[0..ADDRESS_CHECKSUM_LEN].to_vec()
    }
}

impl Wallets {
    pub fn new_wallets() -> Wallets {
        let mut wallets: HashMap<String, Wallet> = HashMap::new();
        let file = OpenOptions::new().create(true).read(true).append(true).open("./addr.txt").unwrap();
        let mut reader = BufReader::new(&file);

        for line in reader.lines() {
            let line = line.unwrap();
            let mut iter = line.split_whitespace();
            let addr= iter.next().unwrap().to_string();
            let public_key_s= iter.next().unwrap().to_string();
            let private_key_s= iter.next().unwrap().to_string();

            let private_key_s = push_key_label(&private_key_s.to_string(), "PRIVATE");
            let wallet = Wallet {
                private_key: private_key_s.parse::<SecretKey>().unwrap(),
                public_key: base58decode(&public_key_s),
            };
            wallets.insert(addr, wallet);
            // println!("key={}, value={}", key, value);
        }

        Wallets {
            wallets,
        }
    }

    pub fn create_wallet(&mut self) -> String {
        let wallet = Wallet::new_wallet();
        let address = wallet.get_address();

        self.wallets.insert(address.clone(), wallet);
        self.persists_wallets();

        address
    }

    pub fn get_wallet(&self, address: &String) -> &Wallet {
        self.wallets.get(address).unwrap()
    }

    fn persists_wallets(&self) {
        let mut file = OpenOptions::new().create(true).read(true).write(true).open("./addr.txt").unwrap();
        for addr in self.wallets.keys() {
            let wallet = self.wallets.get(addr).unwrap();

            let private_key = wallet.private_key
                .to_pkcs8_pem(Default::default())
                .unwrap()
                .to_string();
            let private_key = clear_key_raw_str(&private_key);
            file.write((addr.clone() + " " + &*base58encode(&wallet.public_key) + " " + private_key.as_str() + "\n").as_bytes()).unwrap();
        }
        file.flush().unwrap();
    }
}

#[test]
fn new_key_pair() {
    // Generate secret key
    let secret_key = SecretKey::random(&mut OsRng);

    // Store secret key
    let secret_key_serialized = secret_key
        .to_pkcs8_pem(Default::default())
        .unwrap()
        .to_string();
    println!("Secret Key: \n{}", secret_key_serialized);

    // Load secret key
    let secret_key = secret_key_serialized.parse::<SecretKey>().unwrap();

    // Derive public key
    let public_key = secret_key.public_key();

    // Store public key
    let public_key_serialized = public_key.to_string();
    println!("Public Key: \n{}", public_key_serialized);

    // Load public key
    let public_key = public_key_serialized.parse::<PublicKey>().unwrap();

    // Signing
    let signing_key: SigningKey = secret_key.into();
    let message = b"ECDSA proves knowledge of a secret number in the context of a single message";
    let signature = signing_key.sign(message);
    let sv=signature.to_string().into_bytes();
    let signature_s = String::from_utf8(sv.clone()).unwrap();
    let signature=signature_s.parse::<Signature>().unwrap();

    // Verification
    let verifying_key: VerifyingKey = public_key.into();
    assert!(verifying_key.verify(message, &signature).is_ok());
}